package com.blog.cloud.auth.oauth.request;

import com.alibaba.nacos.common.http.HttpUtils;
import com.blog.cloud.auth.oauth.cache.AuthDefaultStateCache;
import com.blog.cloud.auth.oauth.cache.AuthSource;
import com.blog.cloud.auth.oauth.cache.AuthStateCache;
import com.blog.cloud.auth.oauth.config.AuthConfig;
import com.blog.cloud.auth.oauth.enums.AuthResponseStatus;
import com.blog.cloud.auth.oauth.exception.AuthException;
import com.blog.cloud.auth.oauth.model.AuthCallback;
import com.blog.cloud.auth.oauth.model.AuthResponse;
import com.blog.cloud.auth.oauth.model.AuthToken;
import com.blog.cloud.auth.oauth.model.AuthUser;
import com.blog.cloud.auth.oauth.utils.AuthChecker;
import com.xkcoding.http.util.UrlUtil;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.utils.UrlBuilder;
import me.zhyd.oauth.utils.UuidUtils;
import org.apache.commons.lang3.StringUtils;


import java.util.List;

@Slf4j
public abstract class AuthDefaultRequest implements AuthRequest {
    protected AuthConfig config;
    protected AuthSource source;
    protected AuthStateCache authStateCache;

    public AuthDefaultRequest(AuthConfig config, AuthSource source) {
        this(config, source, AuthDefaultStateCache.INSTANCE);
    }

    public AuthDefaultRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
        this.config = config;
        this.source = source;
        this.authStateCache = authStateCache;
        if (!AuthChecker.isSupportedAuth(config, source)) {
            throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE, source);
        } else {
            AuthChecker.checkConfig(config, source);
        }
    }

    /* 获取 access_token */
    protected abstract AuthToken getAccessToken(AuthCallback authCallback);

    /* 获取用户信息 */
    protected abstract AuthUser getUserInfo(AuthToken authToken);

    @Override
    public AuthResponse<Object> login(AuthCallback authCallback) {
        try {
            AuthChecker.checkCode(this.source, authCallback);
            if (!this.config.isIgnoreCheckState()) {
                AuthChecker.checkState(authCallback.getState(), this.source, this.authStateCache);
            }

            AuthToken authToken = this.getAccessToken(authCallback);
            AuthUser user = this.getUserInfo(authToken);
            return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
        } catch (Exception var4) {
            Exception e = var4;
            log.error("Failed to login with oauth authorization.", e);
            return this.responseError(e);
        }
    }

    private AuthResponse<Object> responseError(Exception e) {
        int errorCode = AuthResponseStatus.FAILURE.getCode();
        String errorMsg = e.getMessage();
        if (e instanceof AuthException) {
            AuthException authException = (AuthException)e;
            errorCode = authException.getErrorCode();
            if (StringUtils.isNotEmpty(authException.getErrorMsg())) {
                errorMsg = authException.getErrorMsg();
            }
        }

        return AuthResponse.builder().code(errorCode).msg(errorMsg).build();
    }

    @Override
    public String authorize(String state) {
        return UrlBuilder.fromBaseUrl(this.source.authorize()).queryParam("response_type", "code").queryParam("client_id", this.config.getClientId()).queryParam("redirect_uri", this.config.getRedirectUri()).queryParam("state", this.getRealState(state)).build();
    }

    protected String accessTokenUrl(String code) {
        return UrlBuilder.fromBaseUrl(this.source.accessToken()).queryParam("code", code).queryParam("client_id", this.config.getClientId()).queryParam("client_secret", this.config.getClientSecret()).queryParam("grant_type", "authorization_code").queryParam("redirect_uri", this.config.getRedirectUri()).build();
    }

    protected String refreshTokenUrl(String refreshToken) {
        return UrlBuilder.fromBaseUrl(this.source.refresh()).queryParam("client_id", this.config.getClientId()).queryParam("client_secret", this.config.getClientSecret()).queryParam("refresh_token", refreshToken).queryParam("grant_type", "refresh_token").queryParam("redirect_uri", this.config.getRedirectUri()).build();
    }

    protected String userInfoUrl(AuthToken authToken) {
        return UrlBuilder.fromBaseUrl(this.source.userInfo()).queryParam("access_token", authToken.getAccessToken()).build();
    }

    protected String revokeUrl(AuthToken authToken) {
        return UrlBuilder.fromBaseUrl(this.source.revoke()).queryParam("access_token", authToken.getAccessToken()).build();
    }

    protected String getRealState(String state) {
        if (StringUtils.isEmpty(state)) {
            state = UuidUtils.getUUID();
        }

        this.authStateCache.cache(state, state);
        return state;
    }

    protected String doPostAuthorizationCode(String code) {
        return (new HttpUtils(this.config.getHttpConfig())).post(this.accessTokenUrl(code));
    }

    protected String doGetAuthorizationCode(String code) {
        return (new HttpUtils(this.config.getHttpConfig())).get(this.accessTokenUrl(code));
    }

    protected String doGetUserInfo(AuthToken authToken) {
        return (new HttpUtils(this.config.getHttpConfig())).get(this.userInfoUrl(authToken));
    }

    protected String doGetRevoke(AuthToken authToken) {
        return (new HttpUtils(this.config.getHttpConfig())).get(this.revokeUrl(authToken));
    }

    protected String getScopes(String separator, boolean encode, List<String> defaultScopes) {
        List<String> scopes = this.config.getScopes();
        if (null == scopes || scopes.isEmpty()) {
            if (null == defaultScopes || defaultScopes.isEmpty()) {
                return "";
            }

            scopes = defaultScopes;
        }

        if (null == separator) {
            separator = " ";
        }

        String scopeStr = String.join(separator, scopes);
        return encode ? UrlUtil.urlEncode(scopeStr) : scopeStr;
    }
}
